// SPDX-FileCopyrightText: Copyright 2025-2025 深圳市同心圆网络有限公司
// SPDX-License-Identifier: GPL-3.0-only

package service_impl

import (
	"fmt"
	"io"
	"net/url"
	"os"
	"sync"
	"time"

	"gitcode.com/openseaotter/so_server/storage"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

type UploadInfo struct {
	DestFile *os.File
	LastTime int64 //上一次写入时间
}

var _uploadMap sync.Map

func ProcessInitUpload(c *gin.Context) {
	groupName := c.Param("group")
	imageName := c.Param("name")
	digest := c.Query("digest")

	//TODO check mount blob

	//检查digest
	if digest != "" {
		_, err := GetDigestAlgo(digest)
		if err != nil {
			SendInvalidDigestError(c, "invalid digest", digest)
			return
		}
	}
	groupInfo, err := storage.ImageGroupDao.Get(c.Request.Context(), groupName)
	if err != nil {
		SendError(c, 403, ERROR_CODE_DENIED, "image group not exist")
		return
	}
	//检查用户是否可以push
	secName := GetSecretUsername(c)
	allow := false
	for _, secretName := range groupInfo.PermInfo.PushSecretNameList {
		if secName == secretName {
			allow = true
			break
		}
	}
	if !allow {
		SendError(c, 403, ERROR_CODE_DENIED, "not allow push")
		return
	}
	//创建镜像信息
	err = CreateImageInfo(c.Request.Context(), groupName, imageName)
	if err != nil {
		SendError(c, 400, ERROR_CODE_NAME_UNKNOWN, err.Error())
		return
	}

	uploadUuid := uuid.NewString()

	if c.Request.ContentLength > 0 && digest != "" { //完整的上传
		err = storage.BlobStore.Save(c.Request.Body, digest)
		if err != nil {
			SendError(c, 400, ERROR_CODE_BLOB_UPLOAD_INVALID, err.Error())
			return
		}
		c.Header("Docker-Upload-UUID", uploadUuid)
		c.Status(201)
		return
	} else {
		filePath := storage.CalcUploadFile(uploadUuid)
		f, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
		if err != nil {
			SendError(c, 400, ERROR_CODE_BLOB_UPLOAD_INVALID, err.Error())
			return
		}

		_uploadMap.Store(uploadUuid, &UploadInfo{
			DestFile: f,
			LastTime: time.Now().UnixMilli(),
		})
		c.Header("Location", fmt.Sprintf("/v2/%s/%s/blobs/uploads/%s", url.PathEscape(groupName), url.PathEscape(imageName), url.PathEscape(uploadUuid)))
		c.Header("Range", "0-0")
		c.Header("Docker-Upload-UUID", uploadUuid)
		c.Status(202)
		return
	}
}

func ProcessUploadBlob(c *gin.Context) {
	groupName := c.Param("group")
	imageName := c.Param("name")
	uploadUuid := c.Param("uuid")

	groupInfo, err := storage.ImageGroupDao.Get(c.Request.Context(), groupName)
	if err != nil {
		SendError(c, 403, ERROR_CODE_DENIED, "image group not exist")
		return
	}
	//检查用户是否可以push
	secName := GetSecretUsername(c)
	allow := false
	for _, secretName := range groupInfo.PermInfo.PushSecretNameList {
		if secName == secretName {
			allow = true
			break
		}
	}
	if !allow {
		SendError(c, 403, ERROR_CODE_DENIED, "not allow push")
		return
	}

	uploadInfoValue, ok := _uploadMap.Load(uploadUuid)
	if !ok {
		SendError(c, 404, ERROR_CODE_BLOB_UPLOAD_UNKNOWN, "upload uuid not exist")
		return
	}
	uploadInfo := uploadInfoValue.(*UploadInfo)
	uploadInfo.LastTime = time.Now().UnixMilli()
	_uploadMap.Store(uploadUuid, uploadInfo)

	data, err := io.ReadAll(c.Request.Body)
	if err != nil {
		SendError(c, 400, ERROR_CODE_BLOB_UPLOAD_INVALID, err.Error())
		return
	}
	_, err = uploadInfo.DestFile.Write(data)
	if err != nil {
		SendError(c, 400, ERROR_CODE_BLOB_UPLOAD_INVALID, err.Error())
		return
	}
	pos, err := uploadInfo.DestFile.Seek(0, io.SeekCurrent)
	if err != nil {
		SendError(c, 400, ERROR_CODE_BLOB_UPLOAD_INVALID, err.Error())
		return
	}
	c.Header("Location", fmt.Sprintf("/v2/%s/%s/blobs/uploads/%s", url.PathEscape(groupName), url.PathEscape(imageName), url.PathEscape(uploadUuid)))
	c.Header("Range", fmt.Sprintf("0-%d", pos))
	c.Header("Docker-Upload-UUID", uploadUuid)
	c.Status(204)
}

func ProcessDoneUpload(c *gin.Context) {
	groupName := c.Param("group")
	imageName := c.Param("name")
	uploadUuid := c.Param("uuid")
	digest := c.Query("digest")

	groupInfo, err := storage.ImageGroupDao.Get(c.Request.Context(), groupName)
	if err != nil {
		SendError(c, 403, ERROR_CODE_DENIED, "image group not exist")
		return
	}
	//检查用户是否可以push
	secName := GetSecretUsername(c)
	allow := false
	for _, secretName := range groupInfo.PermInfo.PushSecretNameList {
		if secName == secretName {
			allow = true
			break
		}
	}
	if !allow {
		SendError(c, 403, ERROR_CODE_DENIED, "not allow push")
		return
	}

	uploadInfoValue, ok := _uploadMap.Load(uploadUuid)
	if !ok {
		SendError(c, 404, ERROR_CODE_BLOB_UPLOAD_UNKNOWN, "upload uuid not exist")
		return
	}
	uploadInfo := uploadInfoValue.(*UploadInfo)
	pos, err := uploadInfo.DestFile.Seek(0, io.SeekCurrent)
	if err != nil {
		SendError(c, 400, ERROR_CODE_BLOB_UPLOAD_INVALID, err.Error())
		return
	}
	_uploadMap.Delete(uploadUuid)
	fileNeme := storage.CalcUploadFile(uploadUuid)
	defer os.Remove(fileNeme)   //skip error check
	uploadInfo.DestFile.Close() //skp error check
	r, err := os.Open(fileNeme)
	if err != nil {
		SendError(c, 400, ERROR_CODE_BLOB_UPLOAD_INVALID, err.Error())
		return
	}

	defer r.Close()
	err = storage.BlobStore.Save(r, digest)
	if err != nil {
		SendError(c, 400, ERROR_CODE_BLOB_UPLOAD_INVALID, err.Error())
		return
	}

	c.Header("Location", fmt.Sprintf("/v2/%s/%s/blobs/uploads/%s", url.PathEscape(groupName), url.PathEscape(imageName), url.PathEscape(uploadUuid)))
	c.Header("Content-Range", fmt.Sprintf("0-%d", pos))
	c.Header("Docker-Content-Digest", digest)
	c.Status(204)
}

func ProcessStatUpload(c *gin.Context) {
	groupName := c.Param("group")
	uploadUuid := c.Param("uuid")

	groupInfo, err := storage.ImageGroupDao.Get(c.Request.Context(), groupName)
	if err != nil {
		SendError(c, 403, ERROR_CODE_DENIED, "image group not exist")
		return
	}
	//检查用户是否可以push
	secName := GetSecretUsername(c)
	allow := false
	for _, secretName := range groupInfo.PermInfo.PushSecretNameList {
		if secName == secretName {
			allow = true
			break
		}
	}
	if !allow {
		SendError(c, 403, ERROR_CODE_DENIED, "not allow push")
		return
	}

	uploadInfoValue, ok := _uploadMap.Load(uploadUuid)
	if !ok {
		SendError(c, 404, ERROR_CODE_BLOB_UPLOAD_UNKNOWN, "upload uuid not exist")
		return
	}
	uploadInfo := uploadInfoValue.(*UploadInfo)
	uploadInfo.LastTime = time.Now().UnixMilli()
	_uploadMap.Store(uploadUuid, uploadInfo)

	pos, err := uploadInfo.DestFile.Seek(0, io.SeekCurrent)
	if err != nil {
		SendError(c, 400, ERROR_CODE_BLOB_UPLOAD_INVALID, err.Error())
		return
	}
	c.Header("Range", fmt.Sprintf("0-%d", pos))
	c.Header("Docker-Upload-UUID", uploadUuid)
	c.Status(204)
}

func ProcessStopUpload(c *gin.Context) {
	groupName := c.Param("group")
	uploadUuid := c.Param("uuid")

	groupInfo, err := storage.ImageGroupDao.Get(c.Request.Context(), groupName)
	if err != nil {
		SendError(c, 403, ERROR_CODE_DENIED, "image group not exist")
		return
	}
	//检查用户是否可以push
	secName := GetSecretUsername(c)
	allow := false
	for _, secretName := range groupInfo.PermInfo.PushSecretNameList {
		if secName == secretName {
			allow = true
			break
		}
	}
	if !allow {
		SendError(c, 403, ERROR_CODE_DENIED, "not allow push")
		return
	}

	uploadInfoValue, ok := _uploadMap.Load(uploadUuid)
	if !ok {
		c.Status(204)
		return
	}
	uploadInfo := uploadInfoValue.(*UploadInfo)
	_uploadMap.Delete(uploadUuid)
	fileNeme := storage.CalcUploadFile(uploadUuid)
	defer os.Remove(fileNeme)   //skip error check
	uploadInfo.DestFile.Close() //skp error check
	c.Status(204)
}

func clearUpload() {
	nowTime := time.Now().UnixMilli()
	removeKeyList := []any{}
	_uploadMap.Range(func(key, value any) bool {
		uploadInfo := value.(*UploadInfo)
		if nowTime-uploadInfo.LastTime > 10*60*1000 { //10分钟
			removeKeyList = append(removeKeyList, key)
		}
		return true
	})
	for _, key := range removeKeyList {
		value, ok := _uploadMap.Load(key)
		if !ok {
			continue
		}
		_uploadMap.Delete(key)
		uploadInfo := value.(*UploadInfo)
		if uploadInfo.DestFile != nil {
			uploadInfo.DestFile.Close() //skip error check
			uploadInfo.DestFile = nil
		}
	}
}

func runClearUpload() {
	for {
		time.Sleep(1 * time.Minute)
		clearUpload()
	}
}
func init() {
	go runClearUpload()
}
